這幾天我們把最陽春的 PokeThreads 做出來了,也解決了 Feed 該回傳什麼內容的問題,但整個架構其實還停留在最原始的狀態:
瀏覽器 -> Server -> Database
只有一台 Server,所有使用者的 Request 都打到同一個地方。當時的假設是使用者只有 100 人,這台 Server 綽綽有餘,但如果使用者從 100 人變成 10 萬人、100 萬人呢?
即使 Database 還撐得住,這一台 Server 本身的運算能力終究有極限,於是我們要面對一個很基本、卻也很重要的問題:
當一台 Server 不夠用了,我們該怎麼辦?
假設現在的 Server 規格是:
流量增加時,最直接的想法就是換一台更強的機器:
這種做法叫做 Vertical Scaling(垂直擴展),也稱為 Scale Up。
它最大的優點是完全不用改架構:
原本:瀏覽器 -> Server -> Database
現在:瀏覽器 -> 更強的 Server -> Database
不需要處理 Request 該分配到哪一台,也不用多準備任何新元件。
自助餐裝不下?盤子加大就對了,就是這麼簡單粗暴。
對早期的小型系統來說,這其實是很合理的第一步。
但 Vertical Scaling 有兩個很明顯的天花板。
第一,硬體有極限。CPU、Memory 不可能無限往上疊,即使雲端廠商能提供非常誇張規格的機器,也不代表我們能無限制地升級下去。
第二,也是更關鍵的一個問題:Single Point of Failure(單點故障)。
現在所有 Request 都由這一台 Server 處理,可是一旦它當機,不管是網路問題、硬體故障還是 OS 出包,所有使用者都會連不上我們的服務,整個網站直接烙賽,SRE 頭殼𢯾咧燒。
就算這台 Server 效能再好,只要世界上只有一台,風險就一直都在。
既然把單一機器越養越大會撞到天花板,那換個方向:不要只養一隻大的,改養一群。
原本只有:
Server 1
現在變成:
Server 1
Server 2
Server 3
這就是 Horizontal Scaling(水平擴展),也叫 Scale Out。
但問題馬上就來了:現在有三台 Server,使用者的 Request 到底該送去哪一台?
這時候需要引入一個新元件:Load Balancer(負載平衡器)。
可以把它想成銀行大廳的號碼牌機,客人(Request)進門先抽號碼牌,系統再把客人平均分配給空著的櫃檯(Server),客人不需要知道今天有幾位櫃員在上班,只要跟著號碼牌走就好。
Load Balancer 放在 Client 與 Application Server 之間:

使用者只知道自己連上了「一個服務」,DNS 解析到的其實是 Load Balancer 的位址,實際上背後有幾台 Server 在工作,對使用者來說是透明的。
假設現在有 Server A、B、C,最直覺的分配方式是輪流發放號碼牌:
Request 1 → Server A
Request 2 → Server B
Request 3 → Server C
Request 4 → Server A
Request 5 → Server B
Request 6 → Server C
這種做法就是 Round Robin,既簡單又公平,是很多 Load Balancer 的預設策略。
銀行號碼牌機還有一個很重要的功能:如果某個櫃檯的行員臨時離開,系統不會再把新客人分配過去。
Load Balancer 也是一樣的邏輯,它會定期對每一台 Server 送出 Health Check(健康檢查),確認對方是否還活著、是否還能正常處理 Request。
假設 Server B 掛掉了:
Browser
↓ ┌── Server A ✓
Load Balancer ────┼── Server B ✗
└── Server C ✓
Load Balancer 一旦偵測到 Server B 沒有回應,就會停止把新 Request 導向它,直到它恢復正常為止。這讓系統具備了初步的 Fault Tolerance(容錯能力),少了一台 Server,服務還是能繼續運作,只是整體處理能力會下降。
加上 Load Balancer 和多台 Server 之後,架構從最初的樣子:
瀏覽器 -> Server -> Database
演化成:
瀏覽器 -> Load Balancer -> Server(多台)-> Database
具體來說,我們現在具備了:
把兩種擴展方式放在一起比較:
| Vertical Scaling | Horizontal Scaling | |
|---|---|---|
| 做法 | 把機器變強 | 增加機器數量 |
| 別名 | Scale Up | Scale Out |
| 架構複雜度 | 低 | 較高(需要 Load Balancer) |
| 擴展上限 | 受硬體限制 | 可持續增加節點 |
| Fault Tolerance | 較弱(單點故障) | 較容易做到 |
| 適合場景 | 小型、早期系統 | 高流量、大型系統 |
要提醒的是,這不是「Horizontal Scaling 永遠比較好」的結論。
實務上兩者經常一起使用,例如先把單台 Server 規格拉到一個合理甜蜜點,再用多台這種規格的機器做水平擴展。
真正該問的問題從來不是「哪一種比較好」,而是現在的瓶頸,需要哪一種擴展方式來解決。
今天解決的是 Application Server 這一層的擴展問題,但這裡其實埋了一個還沒拆開的細節。
回想 Day 2,我們是把使用者的登入狀態(Session)直接存在 Server 的 Memory 裡。
現在 Server 從一台變成了三台,如果 Pikachu 的第一個 Request 被分配到 Server A,第二個 Request 卻被分配到 Server B,而 Server B 的 Memory 裡根本沒有 Pikachu 的 Session,它還認得出這是誰嗎?
這是明天要處理的問題。